package com.ithinks.flow.common.utils.protocol.socket.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

/**
 * @Description
 * @ClassName NioServerHandler
 * @Author MR.LI
 * @date 2020.12.08 17:58
 */
public class NioServerHandler implements Runnable {

    //Selector的英文含义是“选择器”，也可以称为为“轮询代理器”、“事件订阅器”、“channel容器管理机”都行。
    //事件订阅和Channel管理：
    //应用程序将向Selector对象注册需要它关注的Channel，以及具体的某一个Channel会对哪些IO事件感兴趣。
    // Selector中也会维护一个“已经注册的Channel”的容器。
    private Selector selector;

    //通道，被建立的一个应用程序和操作系统交互事件、传递内容的渠道（注意是连接到操作系统）。
    // 那么既然是和操作系统进行内容的传递，那么说明应用程序可以通过通道读取数据，也可以通过通道向操作系统写数据
    private ServerSocketChannel serverSocketChannel;

    private volatile boolean started;

    public NioServerHandler(int port){
        try {
            selector = Selector.open();
            serverSocketChannel = ServerSocketChannel.open();
            serverSocketChannel.configureBlocking(false);
            serverSocketChannel.socket().bind(new InetSocketAddress(port));
            serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
            started = true;
            System.out.println("服务器已启动，端口号："+port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void stop(){
        started = false;
    }
    @Override
    public void run() {
        //循环遍历selector
        while(started){
            try {
                //阻塞，只有当至少一个注册的事件发生的时候才会继续
                selector.select();
                Set<SelectionKey> keys = selector.selectedKeys();
                Iterator<SelectionKey> it = keys.iterator();
                SelectionKey key = null;
                while(it.hasNext()){
                    key = it.next();
                    it.remove();
                    handleInput(key);

                }
            } catch (IOException e) {
                e.printStackTrace();
            }

        }
        if(selector != null)
            try{
                selector.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
    }
    private void handleInput(SelectionKey key) throws IOException {
        if(key.isValid()){
            //处理新接入的请求消息
            if(key.isAcceptable()){
                //获取关心当前时间的channel
                ServerSocketChannel ssc = (ServerSocketChannel)key.channel();
                //通过ServerSocketChannel的accept方法创建socketChannel实例
                //完成该操作意味着完成tcp三次握手，TCP物理链路正式创建
                SocketChannel sc =ssc.accept();
                System.out.println("===========socket channel建立连接==========");
                //设置为非阻塞模式
                sc.configureBlocking(false);
                //连接已完成，可以关心读事件
                sc.register(selector,SelectionKey.OP_READ);
            }
            //读消息
            if(key.isReadable()){
                System.out.println("===========socket channel 数据准备完成============");
                SocketChannel sc = (SocketChannel)key.channel();
                //创建bytebuffer，并开辟一个1M的缓存区
                ByteBuffer buffer  = ByteBuffer.allocate(1024);
                //读取请求码流，返回读取到的字节数
                int readBytes = sc.read(buffer);
                if(readBytes>0){
                    //将缓冲区当前的limit设置为position=0
                    //用于后续对缓冲区的读取操作
                    buffer.flip();
                    //根据缓冲区刻度字节数，创建直接数组
                    byte[] bytes = new byte[buffer.remaining()];
                    //将缓冲区可读直接数组复制到新建的数组中
                    buffer.get(bytes);
                    String message = new String(bytes,"UTF-8");
                    System.out.println("服务器接收到消息：" +message);
                    //处理数据
                    String result = response(message);
                    doWrite(sc,result);
                }
                //链路关闭释放资源
                else if(readBytes < 0 ){
                    key.cancel();
                    sc.close();
                }
            }


        }
    }

    private void doWrite(SocketChannel channel,String response) throws IOException {
        //将消息编码为字节数组
        byte[] bytes = response.getBytes();
        //根据数组容量创建ByteBuffer
        ByteBuffer writeBuffer = ByteBuffer.allocate(bytes.length);
        //将字节数组复制到缓冲区
        writeBuffer.put(bytes);
        //flip操作
        writeBuffer.flip();
        //发送缓冲区的字节数组
        channel.write(writeBuffer);
    }

    //返回给客户端的应答
    public static String response(String msg){
        return "Hello,"+msg+",Now is "+new java.util.Date(
                System.currentTimeMillis()).toString() ;
    }
}
